//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//==========================================================================
// Copyright (c) 2000-2004,  Elastos, Inc.  All Rights Reserved.
//**author:hanxu
//**y/m/d:2005/5/26
//==========================================================================

#include <ddk.h>
#include <stdlib.h>
#include "vga.h"
#include "tvia2010.h"
#include "font_8x16.h"

extern "C" int tvia2010_init (int fbaddress, int ioaddress, MODEID mode);

static uint32_t FrameBufAddr = 0xb3000000;
static uint32_t VgaIoAddr = 0x00000000;
uint32_t g_ScrWidth;

ECode vga_init()
{
    kprintf("entering tvia2010 initiation......\n");
    tvia2010_init (FrameBufAddr, 0, MODE_800x600x16x50);
	kprintf("tvia2010_init() complete.\n");

	return NOERROR;
}

//
// The vga driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateVGA(uint_t uDeviceNo, void *pvParameter)
{
    Video *pDriver = new Video;
    if (NULL == pDriver) {
        assert(0 && "There is not enough memeory!");
        return NULL;
    }

    ECode ec;
    ec = vga_init();

    if(FAILED(ec))
    {
        assert(0 && "vga initialise fault!");
        return NULL;
    }

    kprintf("create vga OK!\n");

    pDriver->m_uFrameBufBase = FrameBufAddr;

    g_ScrWidth = pDriver->m_uScrWidth;
    pDriver->m_bGDIMODE = false;

    // clean screen
    for(uint32_t i = 0;
        i < pDriver->m_uScrWidth * pDriver->m_uScrHeight * pDriver->m_uScrBpp / 8; //800*600*16/2
            i++)
    *(uint8_t *)(pDriver->m_uFrameBufBase + i) = 0;

    pDriver->AddRef();

    return pDriver;
}

ECode Video::Read(
        /* [in] */ UInt64 u64Offset,
        /* [in] */ UInt32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode Video::Write(
        /* [in] */ UInt64 u64Offset, // start address
        /* [in] */ const MemoryBuf & buffer, // print context
        /* [out] */ UInt32 * pBytesWritten, // printed
        /* [out] */ IEvent * * ppCompletionEvent)
{
    wchar_t *pwcString = (wchar_t *)buffer.GetPayload();
    uint32_t uLength = buffer.GetUsed()/2;

    if(m_bGDIMODE) return NOERROR;

    uint32_t buffer;
    uint32_t txtBuf = m_uScrTxtCols * m_uScrTxtRows;

    if (0 == uLength || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    // If appointment start address is bigger than the screen buffer, the
    // Cursor immovability.
    if (u64Offset < m_uScrTxtCols * m_uScrTxtRows) {
        m_uTxtCursorLocation = (uint32_t)u64Offset;
    }
    else {
        u64Offset = m_uTxtCursorLocation;
    }

    buffer = m_uFrameBufBase +
              (m_uTxtCursorLocation / m_uScrTxtCols) * m_uCharHeight * m_uScrWidth *2 +
               (m_uTxtCursorLocation % m_uScrTxtCols) * m_uCharWidth *2;

    // Output the string
    for(uint32_t i = 0; (i < uLength) && (m_uTxtCursorLocation < txtBuf); i++) {

        DrawChar((char)pwcString[i], buffer, m_u16DrawColor);
        m_uTxtCursorLocation++;
        buffer += m_uCharWidth*2;

        if((!(m_uTxtCursorLocation%m_uScrTxtCols))&&(m_uTxtCursorLocation > 0))
            buffer += m_uScrTxtCols * m_uCharHeight * m_uCharWidth*2 - m_uScrTxtCols * m_uCharWidth*2;
    }

    if (pBytesWritten) {
        *pBytesWritten = (m_uTxtCursorLocation - (uint32_t)u64Offset);
        assert(*pBytesWritten >= 0);
    }

    return NOERROR;
}

ECode Video::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;

    if ((nControlCode < 0) || (nControlCode > 9)
        || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    switch(nControlCode)
    {
        // Set the cursor's position
        case 0:{
            if (inBuffer.GetUsed() < (Int32)sizeof(uint32_t)) {
                return E_INVALID_ARGUMENT;
            }

            uint32_t uPosition = *(uint32_t *)inBuffer.GetPayload();

            if (uPosition >= m_uScrTxtCols * m_uScrTxtRows) {
                return E_INVALID_ARGUMENT;
            }
//            SetCursorLocation(uPosition);
            m_uTxtCursorLocation = uPosition;

            break;
        }

        // Get the cursor's position
        case 1:{
            if (pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uTxtCursorLocation))) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uTxtCursorLocation;

            pOutBuffer->SetUsed(sizeof(m_uTxtCursorLocation));

            break;
        }

        // Get the screen column
        case 2:{
            if (pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uScrTxtCols))) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScrTxtCols;

            pOutBuffer->SetUsed(sizeof(m_uScrTxtCols));

            break;
        }

        // Get the screen row
        case 3:{
            if (pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uScrTxtRows))) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScrTxtRows;

            pOutBuffer->SetUsed(sizeof(m_uScrTxtRows));

            break;
        }

        // Set the cursor shape
        case 4:{
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }
//            SetCursorShape(*(bool_t *)(wchar_t *)inBuffer);
           break;
        }

        // set draw color
        case 5:{
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }

            uint16_t uColor = *(uint16_t *)inBuffer.GetPayload();

            m_u16DrawColor = uColor;

            break;
        }

        // get draw color
        case 6:{
            if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_u16DrawColor)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_u16DrawColor;

            pOutBuffer->SetUsed(sizeof(m_u16DrawColor));

            break;
        }

        // clean screen
        case 7:{
            for(uint32_t i = 0;
                    i < m_uScrWidth * m_uScrHeight * m_uScrBpp / 8; //800*600*16/2
                        i++)
                *(uint8_t *)(m_uFrameBufBase + i) = 0;

            m_uTxtCursorLocation = 0;
            break;
        }

        //IO_Remap
        case 8:{
            ec = DzIoRemap(
        		0,
//        		PAGE_SIZE,
        		RoundUp2(m_uScrWidth * m_uScrHeight * m_uScrBpp / 8, PAGE_SIZE),
        		m_uFrameBufBase,
        		0,
        		MemoryProtection_RWIO,
        		(Address *)&m_uVirtAddr);

       	    if (FAILED(ec)) {
                m_uVirtAddr = 0;
                return ec;
            }

            assert(0 != m_uVirtAddr);

	        if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
	        pOutBuffer->Copy((Byte *)&m_uVirtAddr,4);

            break;
        }

        // set vga mode
        case 9:{
            uint8_t uMode = *(uint8_t *)inBuffer.GetPayload();

            m_bGDIMODE = (VGATXTMODE == uMode)? false:true;

            break;
        }

        default:
        return E_INVALID_OPTIONS;
    }

    return NOERROR;
}

ECode Video::WritePixel(
        /* [in] */ UInt64 u64Offset, // start address
        /* [in] */ const MemoryBuf & buffer, // print context
        /* [out] */ UInt32 * pBytesWritten, // printed
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

EXTERN void DrawChar(char chr, uint32_t buffer, uint16_t draw_color)
{
    int char_row,char_col;
    char c = chr;

    uint16_t *base = (uint16_t *)buffer;
    uint8_t line;

    if((c > 0x80)||(c < 0x20)) c=0x20;

    for(char_row = 0; char_row < CHAR_HEIGHT; char_row++) {
    	line = FONT[c * CHAR_HEIGHT + char_row];

        // be careful: j should be decrement order, otherwise it will print a mirror char.
    	for(char_col = CHAR_WIDTH; char_col > 0 ; char_col--) {

    	    if (line & 0x1) {
    		    base[char_col] = draw_color;
    		}
    		else{
    		    base[char_col] = 0;
    		}

    	    line = line >> 1;
    	}
    	base += g_ScrWidth;
    }
}
